package org.codingeasy.hotbean;


import java.io.*;
import java.net.URI;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;
import javax.tools.*;
import javax.tools.JavaCompiler.CompilationTask;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

/**
 * java代码动态编译
 * @since 2.0
 */
@Slf4j
@Component
public class JavaDynamicCompile {

  private final static JavaCompiler COMPILER = ToolProvider.getSystemJavaCompiler();

  private static HotClassLoaderManager hotClassLoaderManager;


  @Autowired
  public void setHotClassLoaderManager(
      HotClassLoaderManager hotClassLoaderManager) {
    JavaDynamicCompile.hotClassLoaderManager = hotClassLoaderManager;
  }

  /**
   * 动态编译
   *
   * @param codeContext 代码 内容
   * @param className class名称
   * @return 返回class对象
   */
  public static Class<?> compileAndLoader(String codeContext, String className) {
    JavaFileManagerDecorator fileManager = new JavaFileManagerDecorator(
        COMPILER.getStandardFileManager(diagnostic -> {
          String message = diagnostic.getMessage(null);
          log.info("编译 {} 结果\n{}", codeContext, message);
          switch (diagnostic.getKind()) {
            case ERROR:
              log.error(message);
              throw new RuntimeException(message);
            case WARNING:
              log.warn(message);
              break;
            case NOTE:
              log.info(message);
              break;
          }
        }, null, StandardCharsets.UTF_8));
    //创建编译任务
    CompilationTask task = COMPILER
        .getTask(null, fileManager, null, null, null,
            Arrays.asList(new StringJavaFileObject(codeContext, className)));
    /**
     * 执行编译并生成字节码
     */
    Boolean call = task.call();
    if (!call) {
      throw new RuntimeException("编译失败");
    }
    ByteArrayJavaFileObject byteArrayJavaFileObject = fileManager
        .getByteArrayJavaFileObjects().stream().findFirst().orElse(null);
    if (byteArrayJavaFileObject == null) {
      return null;
    }
    /**
     * 加载对象 ，这里是有问题的，我们应该 是在ClassLoader 中编译，并加载
     */
    try {
      ByteArrayOutputStream outputStream = (ByteArrayOutputStream) byteArrayJavaFileObject
          .openOutputStream();
      byte[] bytes = outputStream.toByteArray();
      return hotClassLoaderManager.reloadClass(className ,bytes);
    } catch (Exception e) {
      throw new RuntimeException(e.getMessage(), e);
    }
  }


  /**
   * 待编译的的输入对象
   */
  static class StringJavaFileObject extends SimpleJavaFileObject {

    private String content;

    /**
     * Construct a SimpleJavaFileObject of the given kind and with the given URI.
     */
    public StringJavaFileObject(String content, String className) {
      super(URI.create("string:///" + className.replace(".", "/") + ".java"), Kind.SOURCE);
      this.content = content;
    }

    @Override
    public CharSequence getCharContent(boolean ignoreEncodingErrors) throws IOException {
      return content;
    }
  }

  /**
   * 字节码输出对象
   */
  static class ByteArrayJavaFileObject extends SimpleJavaFileObject {

    /**
     * Construct a SimpleJavaFileObject of the given kind and with the given URI.
     */
    private final ByteArrayOutputStream outputStream = new ByteArrayOutputStream();

    public ByteArrayJavaFileObject(String name) {
      super(URI.create("bytes:///" + name.replace(".", "/") + ".class"), Kind.CLASS);
    }

    @Override
    public OutputStream openOutputStream() throws IOException {
      return outputStream;
    }
  }


  static class JavaFileManagerDecorator extends ForwardingJavaFileManager<JavaFileManager> {
    // 就是一个装饰着模式  ForwardingJavaFileManager

    /**
     * Creates a new instance of ForwardingJavaFileManager.
     *
     * @param fileManager delegate to this file manager
     */
    public JavaFileManagerDecorator(JavaFileManager fileManager) {
      super(fileManager);
    }

    private final Set<ByteArrayJavaFileObject> byteArrayJavaFileObjects =  new HashSet<>();


    public Set<ByteArrayJavaFileObject>  getByteArrayJavaFileObjects() {
      synchronized (this) {
        return byteArrayJavaFileObjects;
      }
    }

    // 有字节码的输出的时候 我们自定义一个JavaFileObject 来接受输出了
    @Override
    public JavaFileObject getJavaFileForOutput(Location location, String className,
        JavaFileObject.Kind kind, FileObject sibling) throws IOException {
      if (JavaFileObject.Kind.CLASS == kind) {
        ByteArrayJavaFileObject byteArrayJavaFileObject = new ByteArrayJavaFileObject(className);
        synchronized (this) {
          byteArrayJavaFileObjects.add(byteArrayJavaFileObject);
        }
        return byteArrayJavaFileObject;
      } else {
        return super.getJavaFileForOutput(location, className, kind, sibling);
      }
    }
  }
}